05. Translate the Star
L3 04 Translation
Translate the Star
In this task, you will wire up translateButton to an onClick listener, which will cause the star to move back and forth. translateButton calls the function translater(), which is currently empty. Let’s fill that in.
Step 1: Creating the animator
- Inside the
translater()function, create an animation that moves the star to the right by 200 pixels and runs it:
val animator = ObjectAnimator.ofFloat(star, View.TRANSLATION_X, 200f)
animator.start()
- Run the application now. When you click on
TRANSLATE, the star moves to the right… but it doesn’t come back to the center. If you click on the button again, it doesn’t move at all.
What’s going on?
First of all, the animation is only being set up to run one way; it animates the star 200 pixels to the right… and that’s it. So if we want it to come back, we’re going to need something extra.
Also, subsequent animations don’t appear to do anything because the animation is set up to run to a value of 200. After the animation has run, the value is already at 200, so there’s no place else to go.
We can fix both of these problems by using the concept of “repetition.”
Note: Repetition is a way of telling animations to do the same task again and again. You can specify how many times to repeat (or just tell it to run infinitely). You can also specify the repetition behavior, either REVERSE (for reversing the direction every time it repeats) or RESTART (for animating from the original start value to the original end value, thus repeating in the same direction every time).
- You will change the animation to repeat, playing in reverse back to its starting position. Set the
repeatCountproperty on the animation (which controls how many times it repeats after the first run) as well as the type of repetition (REVERSEorRESTARTfor repeating again from/to the same values).
val animator = ObjectAnimator.ofFloat(star, View.TRANSLATION_X, 200f)
animator.repeatCount = 1
animator.repeatMode = ObjectAnimator.REVERSE
animator.start()
- Run the app again and you can see that the button now animates to the right and back. That is, unless you click the button again while it’s running, which causes a problem.
The problem here is different than what we saw with the rotation animation. In that task, the star would snap back to its starting value to begin the animation anew. But here, the animation doesn’t snap at all. Instead, it starts animating from where it’s at, but it doesn’t go as far. For example, if you restart it halfway through its return trip (when it is at 100), then it will start the new animation from 100… but it will still only go to 200. So the overall animation is much shorter because it started from a value greater than the original starting point of 0.
What’s going on?
There’s a subtle difference between this animator and the animator used for the rotation task. The rotation animation was given both start and end values, so it always ran the animation between those two values. Here, the animation is given only an end value. When the animation starts, it first queries the current value of the translation property on star and uses that as its implicit start value, animating from that value to 200. So when you click on the TRANSLATE button when the animation is part of the way through, it grabs that mid-way value as the starting value for the new animation and runs the animation over a smaller distance from there to 200.
Step 2: Prevent restarts while the animation is running
You will fix this in a similar way to how you fixed it for the rotation animation, by disabling the translateButton during the animation so that the animation comes to a rest back at 0 before it can run again.
Since this is the second time you are writing very similar code (adding a listener to enable/disable a button), you should refactor that code into a separate function that you’ll use everywhere you need it.
- Create a function called
disableViewDuringAnimation(), which takes a View and an Animator, and use the code you already wrote earlier inrotater()to create the body of this function:
private fun disableViewDuringAnimation(view: View, animator: ObjectAnimator) {
animator.addListener(object : AnimatorListenerAdapter() {
override fun onAnimationStart(animation: Animator?) {
view.isEnabled = false
}
override fun onAnimationEnd(animation: Animator?) {
view.isEnabled = true
}
})
}
- Now call this method in
translater()androtater()to disable their buttons during their respective animations. Also remove the code that sets the click listener in therotater()function
private fun rotater() {
val animator = ObjectAnimator.ofFloat(star, View.ROTATION,
-360f, 0f)
animator.duration = 1000
disableViewDuringAnimation(rotateButton, animator)
animator.start()
}
private fun translater() {
val animator = ObjectAnimator.ofFloat(star, View.TRANSLATION_X,
200f)
animator.repeatCount = 1
animator.repeatMode = ObjectAnimator.REVERSE
disableViewDuringAnimation(translateButton, animator)
animator.start()
}
- Run the app again. You should now see that the rotation works as before, and that translation also enables/disables its button due to the
View-disabling functionality you’ve added to its animator listener.
Step 3: Refactor into an extension function
As a bonus step, take advantage of Kotlin’s language features by using extension functions.
Change the disableViewDuringAnimation() function to be an extension function on ObjectAnimator. This makes the function more concise to call, since it eliminates a parameter. It also makes the code a little more natural to read, by putting the animator-related functionality directly onto ObjectAnimator:
private fun ObjectAnimator.disableViewDuringAnimation(view: View) {
addListener(object : AnimatorListenerAdapter() {
override fun onAnimationStart(animation: Animator?) {
view.isEnabled = false
}
override fun onAnimationEnd(animation: Animator?) {
view.isEnabled = true
}
})
}
- Modify the code in
translater()to call this extension function:
private fun translater() {
val animator = ObjectAnimator.ofFloat(star, View.TRANSLATION_X,
200f)
animator.repeatCount = 1
animator.repeatMode = ObjectAnimator.REVERSE
animator.disableViewDuringAnimation(translateButton)
animator.start()
}
- Make the same change in
rotater(), calling the new extension function:
private fun rotater() {
val animator = ObjectAnimator.ofFloat(star, View.ROTATION,
-360f, 0f)
animator.duration = 1000
animator.disableViewDuringAnimation(rotateButton)
animator.start()
}